package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.dto.pojo.*;
import com.leyou.item.client.ItemClient;
import com.leyou.search.bo.Goods;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.repository.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author HeGuanChao
 * @Description TODO
 * @Date 2020/4/6 20:17
 */
@Service
public class SearchService {
    @Autowired
    private ItemClient itemClient;
    @Autowired
    private GoodsRepository repository;
    @Autowired
    private ElasticsearchTemplate esTemplate;

    /**
     * 把一个spu转为goods对象
     *
     * @param spu
     * @return
     */
    public Goods buildGoods(SpuDTO spu) {
        Long spuId = spu.getId();
        //1、spu下所有的sku
        List<SkuDTO> skuDTOList = itemClient.querySkuListBySpuId(spuId);
        // 1.1 创建一个list集合用于保存sku规格参数
        List<Map<String, Object>> skuList = new ArrayList<>();
        for (SkuDTO skuDTO : skuDTOList) {
            Map<String, Object> map = new HashMap<>();
            map.put("id", skuDTO.getId());
            // 拼接images并用逗号分隔
            map.put("image", StringUtils.substringBefore(skuDTO.getImages(), ","));
            map.put("price", skuDTO.getPrice());
            map.put("title", skuDTO.getTitle());
            // 添加到sku集合
            skuList.add(map);
        }

        // 把对象转为json字符串
        String skuJsonStr = JsonUtils.toString(skuList);

        //2、sku的价格集合
        Set<Long> priceSet = skuDTOList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //3、搜索字段，包含名称、分类、品牌、等
        List<CategoryDTO> categoryDTOList = itemClient.queryByIds(spu.getCategoryIds());
        String categoryName = categoryDTOList.stream().map(CategoryDTO::getName).collect(Collectors.joining());
        // 3.1 查询品牌名称
        BrandDTO brandDTO = itemClient.queryById(spu.getBrandId());
        String all = spu.getName() + categoryName + brandDTO.getName();

        //4、规格参数，包含key和value
        Map<String, Object> specs = new HashMap<>();
        // 4.1 查询规格参数的key
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParamByGid(null, spu.getCid3(), true);
        // 4.2 查询规格参数的value
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailSpuId(spuId);
        // 4.3 取出通用规格参数
        Map<Long, Object> genericSpecMap = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        // 4.4 取出特有的规格参数
        Map<Long, List<String>> specialSpecMap = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        // 4.5 存入specs
        for (SpecParamDTO param : specParamDTOS) {
            // 取出name作为key
            String key = param.getName();
            Object value = null;
            if (param.getGeneric()) {
                // 通用参数
                value = genericSpecMap.get(param.getId());
            } else {
                // 特有
                value = specialSpecMap.get(param.getId());
            }
            // 判断是否是数值类型
            if (param.getNumeric()) {
                value = chooseSegment(value, param);
            }
            specs.put(key, value);
        }

        //5、创建goods对象
        Goods goods = new Goods();
        // 设置商品id
        goods.setId(spuId);
        // 设置副标题
        goods.setSubTitle(spu.getSubTitle());
        // 所有sku规格参数
        goods.setSkus(skuJsonStr);
        // 设置搜索字段，包含名称、分类、品牌等
        goods.setAll(all);
        // 设置品牌id
        goods.setBrandId(spu.getBrandId());
        // 设置分类id
        goods.setCategoryId(spu.getCid3());
        // 设置创建时间
        goods.setCreateTime(spu.getCreateTime().getTime());
        goods.setPrice(priceSet);
        goods.setSpecs(specs);

        //返回goods对象
        return goods;
    }


    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }

    /**
     * 商品搜索
     *
     * @param searchRequest 搜索条件
     * @return 商品分页数据
     */
    public PageResult<GoodsDTO> search(SearchRequest searchRequest) {
        // 1. 判断是否有搜索条件
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }
        // 2. 创建es原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 3. 添加一些过滤条件
        // 3.1 source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));
        // 3.2 搜索条件   matchQuery(): 单个匹配, field不支持通配符, 前缀具高级特性  operator(): 设置使用布尔查询时要使用的操作符
        queryBuilder.withQuery(buildSearchKey(searchRequest));

        // 4. 分页条件
        // es 分页查询从0开始
        int page = searchRequest.getPage() - 1;
        int size = searchRequest.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));
        // 5. 搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 6. 分析结果
        long total = result.getTotalElements();
        int totalPages = result.getTotalPages();
        List<Goods> goodsList = result.getContent();
        // 封装并返回
        return new PageResult<>(total, totalPages, BeanHelper.copyWithCollection(goodsList, GoodsDTO.class));
    }


    public Map<String, List<?>> filter(SearchRequest searchRequest) {
        // 创建一个map集合用于接收es查询出来的数据
        Map<String, List<?>> filterMap = new LinkedHashMap<>();
        // 判断关键字
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.INVALID_PARAM_ERROR);
        }

        // 构建搜索对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 拼接搜索条件
        queryBuilder.withQuery(buildSearchKey(searchRequest));
        // 分页条件
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 指定获取的内容
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));

        // 添加聚合条件
        String categoryAgg = "categoryAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(categoryAgg).field("categoryId"));
        String brandAgg = "brandAgg";
        queryBuilder.addAggregation(AggregationBuilders.terms(brandAgg).field("brandId"));
        // 搜索结果
        AggregatedPage<Goods> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 获取Aggregations
        Aggregations aggregations = aggregatedPage.getAggregations();
        // 取出分类的聚合
        Terms categoryTerm = aggregations.get(categoryAgg);
        // 处理分类的聚合
        List<Long> categoryIds = handlerCategoryFilter(categoryTerm, filterMap);
        // 取出品牌的聚合
        Terms brandTerm = aggregations.get(brandAgg);
        // 处理品牌的聚合
        handleBrandFilter(brandTerm, filterMap);

        // 把规格参数过滤条件加入到filterMap中
        if (categoryIds != null && categoryIds.size() == 1) {
            addSpecParamFilter(categoryIds.get(0), buildSearchKey(searchRequest), filterMap);
        }
        // 返回集合
        return filterMap;
    }

    private void addSpecParamFilter(Long categoryId, QueryBuilder buildSearchKey, Map<String, List<?>> filterMap) {
        // 获取当前分类下的可以作为查询条件的规格参数
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParamByGid(null, categoryId, true);
        // 封装条件的对象
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 聚合查询
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""}, null));
        // 分页
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 添加查询条件
        queryBuilder.withQuery(buildSearchKey);

        // 循环添加组合查询条件
        specParamDTOS.forEach(specParamDTO -> {
            // 得到名称
            String aggName = specParamDTO.getName();
            // 得到field字段
            String aggField = "specs." + aggName;
            // 添加条件
            queryBuilder.addAggregation(AggregationBuilders.terms(aggName).field(aggField).size(1000));
        });
        // 索引库查询
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        // 得到所有的结果
        Aggregations aggregations = result.getAggregations();
        // 遍历解析出数据和结果
        specParamDTOS.forEach(specParamDTO -> {
            // 通过名称取出桶
            String aggName = specParamDTO.getName();
            // 根据聚合后的桶的名称得到桶中数据的集合
            Terms terms = aggregations.get(aggName);
            // 解析桶中数据的集合
            List<String> specList = terms.getBuckets().stream()
                    .map(Terms.Bucket::getKeyAsString)
                    .filter(StringUtils::isNotBlank)
                    .collect(Collectors.toList());
            // 得到的规格参数存入map中
            filterMap.put(aggName, specList);
        });
    }

    private void handleBrandFilter(Terms brandTerm, Map<String, List<?>> filterMap) {
        // 获取品牌id
        List<Long> brandIds = brandTerm.getBuckets().stream()
                .map(Terms.Bucket::getKeyAsNumber)
                .map(Number::longValue)
                .collect(Collectors.toList());
        // 查询品牌列表
        filterMap.put("品牌", itemClient.queryBrandByIds(brandIds));
    }

    private List<Long> handlerCategoryFilter(Terms categoryTerm, Map<String, List<?>> filterMap) {
        List<? extends Terms.Bucket> buckets = categoryTerm.getBuckets();
        // 获取所有分类的id
        List<Long> cIds = new ArrayList<>();
        for (Terms.Bucket bucket : buckets) {
            // 取出key
            long key = bucket.getKeyAsNumber().longValue();
            cIds.add(key);
        }
        // 查询分类的名称
        List<CategoryDTO> categoryDTOList = itemClient.queryByIds(cIds);
        // 存入map
        filterMap.put("分类", categoryDTOList);

        // 返回分类id集合
        return cIds;
    }

    private QueryBuilder buildSearchKey(SearchRequest request) {
        // 创建一个组合条件查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        // 向组合条件查询中封装搜索条件
        boolQueryBuilder.must(QueryBuilders.matchQuery("all", request.getKey()).operator(Operator.AND));

        // 向组合条件查询中封装过滤参数
        Map<String, Object> filters = request.getFilters();
        filters.entrySet().forEach(filter -> {
            String key = filter.getKey();
            Object value = filter.getValue();
            // 对域字段进行处理
            if ("分类".equals(key)) {
                key = "categoryId";
            } else if ("品牌".equals(key)) {
                key = "brandId";
            } else {
                key = "specs." + key;
            }
            boolQueryBuilder.filter(QueryBuilders.termsQuery(key, value));
        });

        // 返回
        return boolQueryBuilder;
    }

    /**
     * 创建索引
     *
     * @param spuId
     */
    public void saveGoods(Long spuId) {
        // 查询spu数据
        SpuDTO spuDTO = itemClient.querySpuById(spuId);
        // 创建goods对象
        Goods goods = buildGoods(spuDTO);
        // 创建es索引
        repository.save(goods);
    }

    /**
     * 删除es索引
     *
     * @param spuId
     */
    public void deleteGoods(Long spuId) {
        repository.deleteById(spuId);
    }
}
